#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

using System;

namespace Grawlix.Services
{
	/// <summary>
	/// A proxy to an object held in a different execution
	/// environment.
	/// </summary>
	/// <typeparam name="T">A base type of object being proxied.
	/// </typeparam>
	public sealed class ExternalProxy<T> : IDisposable
	{
		/// <summary>
		/// Whether the proxy has been disposed.
		/// </summary>
		private bool _disposed;

		/// <summary>
		/// The execution environment of the proxied object.
		/// </summary>
		private IExecutionEnvironment _environment;

		/// <summary>
		/// The object that is being proxied.
		/// </summary>
		private T _target;

		/// <summary>
		/// Initializes a new instance of the 
		/// <see cref="ExternalProxy&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="environment">The environment to hold the
		/// object. The ExternalProxy will take ownership of the
		/// environment and take care of its disposal.</param>
		/// <param name="instanceType">The real type to create.</param>
		/// <param name="arguments">The arguments to pass to the
		/// constructor.</param>
		/// <exception cref="ArgumentNullException"><c>arguments</c> is
		/// null.</exception>
		/// <exception cref="ArgumentNullException"><c>instanceType</c>
		/// is null.</exception>
		/// <exception cref="ArgumentNullException"><c>environment</c>
		/// is null.</exception>
		/// <exception cref="ArgumentException">
		/// <paramref name="instanceType"/> is not a T.</exception>
		public ExternalProxy(IExecutionEnvironment environment,
		                     Type instanceType,
		                     params object[] arguments)
		{
			if (arguments == null)
				throw new ArgumentNullException("arguments");
			if (instanceType == null)
				throw new ArgumentNullException("instanceType");
			if (environment == null)
				throw new ArgumentNullException("environment");
			if (!(typeof(T).IsAssignableFrom(instanceType)))
			{
				throw new ArgumentException(
					"Invalid type (" + instanceType + ") passed during " +
					"the creation of an ExternalProxy. " +
					"Ensure that it derives from " +
					"the generic argument (" + typeof(T) + ").",
					"instanceType");
			}
			_environment = environment;
			_target =
				(T)_environment.CreateInstance(instanceType, arguments);
		}

		/// <summary>
		/// Gets the target of the proxy.
		/// </summary>
		/// <value>The target of the proxy.</value>
		/// <exception cref="ObjectDisposedException">
		/// <see cref="ExternalProxy{T}"/></exception>
		public T Target
		{
			get
			{
				if (_disposed)
					throw new ObjectDisposedException("ExternalProxy");
				return _target;
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing,
		/// releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed
		/// and unmanaged resources; <c>false</c> to release only
		/// unmanaged resources.</param>
		private void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (disposing)
				{
					var target = _target as IDisposable;
					_target = default(T);
					if (target != null)
					{
						target.Dispose();
					}
					if (_environment != null)
					{
						IDisposable environment = _environment;
						_environment = null;
						environment.Dispose();
					}
				}
				_disposed = true;
			}
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the
		/// current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current
		/// <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			if (_disposed)
				return "[Disposed: " + GetType() + "]";
			return "ExternalProxy " + _environment + " " + _target;
		}
	}
}